//
//  SocketRocketUtility.m
//  SUN
//
//  Created by 孙俊 on 17/2/16.
//  Copyright © 2017年 SUN. All rights reserved.
//

#import "SocketRocketUtility.h"
#import "ChannelModel.h"
#import "LFCGzipUtility.h"

#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}

@interface SocketRocketUtility()<SRWebSocketDelegate>
{
    int _index;
    NSTimer * heartBeat;
    NSTimeInterval reConnectTime;
}

@property (nonatomic,strong) SRWebSocket *socket;
@property (nonatomic,copy) NSString *urlString;
@property (nonatomic,copy) NSString *token;
@property (nonatomic,strong) NSNumber *incressID;  //自增长ID
@property (nonatomic,strong) NSMutableDictionary *channelDic; // 频道字典,包括频道名,频道seq,频道epoch
@property (nonatomic,strong) NSArray *channels;  //频道名数组


@end

@implementation SocketRocketUtility

+ (SocketRocketUtility *)instance {
    static SocketRocketUtility *Instance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        Instance = [[SocketRocketUtility alloc] init];
        Instance.incressID = @(1);
        Instance.channelDic = [NSMutableDictionary dictionary];
    });
    return Instance;
}

#pragma mark - **************** public methods
-(void)SRWebSocketOpenWithURLString:(NSString *)urlString
                              token:(NSString *)token
                           channels:(NSArray *)channels {
    
    //如果是同一个url return
    if (self.socket) {
        return;
    }
    if (!urlString || !token || !channels) {
        return;
    }
    self.urlString = urlString;
    self.token = token;
    self.channels = channels;
    self.socket = [[SRWebSocket alloc] initWithURLRequest:
                   [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
    if ([self.delegate respondsToSelector:@selector(scoketTipsMessage:)]) {
        [self.delegate scoketTipsMessage:[NSString stringWithFormat:@"连接的websocket地址：%@",self.socket.url.absoluteString]];
    }
    
    //SRWebSocketDelegate 协议
    self.socket.delegate = self;
    //开始连接
    [self.socket open];
}

- (void)SRWebSocketClose {
    if (self.socket){
        [self.socket close];
        self.socket = nil;
        //断开连接时销毁心跳
        [self destoryHeartBeat];
    }
}

#pragma mark - **************** setter getter
- (SRReadyState)socketReadyState {
    return self.socket.readyState;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#define WeakSelf(ws) __weak __typeof(&*self)weakSelf = self
- (void)sendData:(id)data {
    if ([self.delegate respondsToSelector:@selector(scoketPostMessage:)]) {
        [self.delegate scoketPostMessage:[NSString stringWithFormat:@"发送消息 %@",data]];
    }
    
    WeakSelf(ws);
    dispatch_queue_t queue =  dispatch_queue_create("zy", NULL);//异步串行
    
    dispatch_async(queue, ^{
        if (weakSelf.socket != nil) {
            // 只有 SR_OPEN 开启状态才能调 send 方法啊，不然要崩
            if (weakSelf.socket.readyState == SR_OPEN) {
                [weakSelf.socket send:data];    // 发送数据
                
            } else if (weakSelf.socket.readyState == SR_CONNECTING) {
                NSLog(@"正在连接中，重连后其他方法会去自动同步数据");
                // 每隔2秒检测一次 socket.readyState 状态，检测 10 次左右
                // 只要有一次状态是 SR_OPEN 的就调用 [ws.socket send:data] 发送数据
                // 如果 10 次都还是没连上的，那这个发送请求就丢失了，这种情况是服务器的问题了，小概率的
                // 代码有点长，我就写个逻辑在这里好了
                [self reConnect];
                
            } else if (weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) {
                // websocket 断开了，调用 reConnect 方法重连
                
                NSLog(@"重连");
                
                [self reConnect];
            }
        } else {
            NSLog(@"没网络，发送失败，一旦断网 socket 会被我设置 nil 的");
            NSLog(@"其实最好是发送前判断一下网络状态比较好，我写的有点晦涩，socket==nil来表示断网");
        }
    });
}

#pragma mark - **************** private mothodes
//重连机制
- (void)reConnect {
    [self SRWebSocketClose];

    if (reConnectTime > 5*60) {
        //您的网络状况不是很好，请检查网络后重试
        return;
    }
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.socket = nil;
        if ([self.delegate respondsToSelector:@selector(scoketTipsMessage:)]) {
            [self.delegate scoketTipsMessage:[NSString stringWithFormat:@"重连"]];
        }
        [self SRWebSocketOpenWithURLString:self.urlString token:self.token channels:self.channels];
       
    });
    
    //重连时间2的指数级增长
//    if (reConnectTime == 0) {
//        reConnectTime = 1;
//    } else {
        reConnectTime ++;
//    }
}


//取消心跳
- (void)destoryHeartBeat {
    dispatch_main_async_safe(^{
        if (heartBeat) {
            if ([heartBeat respondsToSelector:@selector(isValid)]){
                if ([heartBeat isValid]){
                    [heartBeat invalidate];
                    heartBeat = nil;
                }
            }
        }
    })
}

//初始化心跳
- (void)initHeartBeat {
    dispatch_main_async_safe(^{
        [self destoryHeartBeat];
        //心跳设置为3分钟，NAT超时一般为5分钟
        heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES];
        //和服务端约定好发送什么作为心跳标识，尽可能的减小心跳包大小
        [[NSRunLoop currentRunLoop] addTimer:heartBeat forMode:NSRunLoopCommonModes];
    })
}

- (void)sentheart {
    [self ping];
}

//pingPong
- (void)ping {
    if (self.socket.readyState == SR_OPEN) {
        NSMutableDictionary *parameter = [NSMutableDictionary dictionary];
        [parameter setObject:@(999) forKey:@"id"];
        [parameter setObject:@(7) forKey:@"method"];
        NSString *jsonStr = [self convertToJsonData:parameter];
        NSData *data = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
        [self.socket sendPing:data];
    }
}

#pragma mark - **************** SRWebSocketDelegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {

    if (webSocket == self.socket) {
        //每次正常连接的时候清零重连时间,自增变1
        reConnectTime = 0;
        self.incressID = @(1);
        //开启心跳
        [self initHeartBeat];
        
        if ([self.delegate respondsToSelector:@selector(scoketTipsMessage:)]) {
            [self.delegate scoketTipsMessage:[NSString stringWithFormat:@"socket %@ 连接成功",self.urlString]];
        }
        NSMutableDictionary *parameter = [NSMutableDictionary dictionary];
        NSMutableDictionary *tokenParameter = [NSMutableDictionary dictionary];
        //开启成功后生成client
        [tokenParameter setObject:self.token forKey:@"token"];
        [parameter setObject:tokenParameter forKey:@"params"];
        [parameter setObject:self.incressID forKey:@"id"];
        NSString *jsonStr = [self convertToJsonData:parameter];
        [self sendData:jsonStr];
    }
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
    if (webSocket == self.socket) {
        if ([self.delegate respondsToSelector:@selector(scoketTipsMessage:)]) {
            [self.delegate scoketTipsMessage:[NSString stringWithFormat:@"socket连接失败"]];
        }
        _socket = nil;
        //连接失败就重连
        if ([self.delegate respondsToSelector:@selector(scoketTipsMessage:)]) {
            [self.delegate scoketTipsMessage:[NSString stringWithFormat:@"连接失败"]];
        }
        
        [self reConnect];
    }
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
    if (webSocket == self.socket) {
        [self SRWebSocketClose];
        if ([self.delegate respondsToSelector:@selector(scoketTipsMessage:)]) {
            [self.delegate scoketTipsMessage:[NSString stringWithFormat:@"被关闭连接，code:%ld,reason:%@,wasClean:%d",(long)code,reason,wasClean]];
            
            // 重连一次
            [self reConnect];
        }
    }
}

/*
 该函数是接收服务器发送的pong消息，其中最后一个是接受pong消息的，
 在这里就要提一下心跳包，一般情况下建立长连接都会建立一个心跳包，
 用于每隔一段时间通知一次服务端，客户端还是在线，这个心跳包其实就是一个ping消息，
 我的理解就是建立一个定时器，每隔十秒或者十五秒向服务端发送一个ping消息，这个消息可是是空的
 */
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
//    NSString *reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding];
//    NSLog(@"reply===%@",reply);
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message  {
    
    if (webSocket == self.socket) {
        if ([message isKindOfClass:[NSString class]]) {
            //数据包粘在一起处理
            NSString *str = [message stringByReplacingOccurrencesOfString:@"}{" withString:@"}\n{"];
            NSArray *strArray = [str componentsSeparatedByString:@"\n"];
            for (NSString *strTMP in strArray) {
                if (strTMP.length) {
                    [self analysisMessage:strTMP];
                }
            }
        }
        
    }
}


/**
 channelDic 频道字典,订阅成功后将频道的信息以频道名为key存入字典
 每收到一条信息,通过收到信息的频道从channelDic中去取出对应的频道,更新频道的seq(没有seq就是不支持历史消息,置空)
 重新订阅频道(重连)
 把channelDic中的频道都重新订阅,如果频道信息seq不为空传seq,epoch(订阅成功后返回的频道信息),recover(YES),这样订阅成功后就会返回seq之后没有收到的历史消息
 */
- (void) analysisMessage:(id) message {
    if ([message isKindOfClass:[NSString class]]) {
        
        NSDictionary *responsDic = [self dictionaryWithJsonString:message];
        NSNumber *indentf = responsDic[@"id"];
        if (indentf) { // token,订阅相关消息
            if (indentf.integerValue == 1) { //token发送成功订阅频道
                if ([self.delegate respondsToSelector:@selector(socketTokenReciveScucess:)]) {
                    [self.delegate socketTokenReciveScucess:[NSString stringWithFormat:@"token认证成功 %@",message]];
                }
                [self subscribChannels];

            } else  { // 订阅频道成功
                [self saveChannelsWithDickies:responsDic];
                [self analysisHistoryDic:responsDic];
            }

        } else {  // 普通消息
            [self analysisNomalMessage:responsDic];
            
        }
                
    } else {
        NSLog(@"接收数据异常");
        if ([self.delegate respondsToSelector:@selector(socketAnalysisMessageFail:)]) {
            [self.delegate socketAnalysisMessageFail:[NSString stringWithFormat:@"socket接收数据异常"]];
        }
    }
    
}


/**
 订阅频道
 */
- (void) subscribChannels {
    
    for (NSString *channel in self.channels) {
        //每订阅一个频道自增1
        self.incressID = [NSNumber numberWithInteger:self.incressID.integerValue+1];
        NSMutableDictionary *parameter = [NSMutableDictionary dictionary];
        NSMutableDictionary *channelParameter = [NSMutableDictionary dictionary];
        ChannelModel *channel_temp = self.channelDic[channel];
        if (channel_temp.seq) {
            // 之前接收过消息
            [channelParameter setObject:@(channel_temp.recoverable) forKey:@"recover"];
            [channelParameter setObject:channel_temp.seq forKey:@"seq"];
            [channelParameter setObject:channel_temp.epoch forKey:@"epoch"];
        }
        [channelParameter setObject:channel forKey:@"channel"];
        [parameter setObject:channelParameter forKey:@"params"];
        [parameter setObject:self.incressID forKey:@"id"];
        [parameter setObject:@(1) forKey:@"method"];
        NSString *jsonStr = [self convertToJsonData:parameter];
        [self sendData:jsonStr];
    }
}


/**
 存储频道信息

 @param responsDic 频道信息字典
 */
- (void) saveChannelsWithDickies:(NSDictionary *) responsDic {
    
    NSDictionary *resultDic = responsDic[@"result"];
    ChannelModel *channel = [ChannelModel new];
    NSNumber *channelID = responsDic[@"id"];
    //根据返回的id生成对应频道  -2 是因为:  1是传token 2开始订阅频道 -2就是从频道数组的第0个元素开始
    channel.channel = self.channels[channelID.integerValue - 2];
    channel.epoch = resultDic[@"epoch"];
    channel.seq = resultDic[@"seq"];
    channel.recoverable = [resultDic[@"recoverable"] boolValue];
    [self.channelDic setObject:channel forKey:channel.channel];
    if ([self.delegate respondsToSelector:@selector(socketConnectChannelScucess:)]) {
        [self.delegate socketConnectChannelScucess:[NSString stringWithFormat:@"订阅频道%@成功",channel.channel]];
    }
}


/**
 订阅成功后解析历史消息

 @param responsDic 频道信息字典(里面解析历史消息)
 */
- (void) analysisHistoryDic:(NSDictionary *) responsDic {
    // 解析历史消息
    NSDictionary *resultDic = responsDic[@"result"];
    NSArray *publications = resultDic[@"publications"];
    if (publications.count) { //  有历史消息
        NSNumber *channelID = responsDic[@"id"];
        //根据返回的id生成对应频道  -2 是因为:  1是传token 2开始订阅频道 -2就是从频道数组的第0个元素开始
        NSString *channelTitle = self.channels[channelID.integerValue - 2];
        ChannelModel *channel_temp = self.channelDic[channelTitle];
        for (NSDictionary *dic in [publications reverseObjectEnumerator]) {
            //更新频道的seq保证seq是最后一条消息的,用于历史消息的seq参数
            channel_temp.seq = dic[@"seq"];
            NSDictionary *inputData = dic[@"data"];
            NSString *inputStr = inputData[@"input"];
            if (inputStr) { //socket消息
                BOOL isUnzip = NO;
                isUnzip = [self unGZIPWithStr:inputStr];
                if (isUnzip) {
                    if ([self.delegate respondsToSelector:@selector(scoketReciveHistoryMessage:)]) {
                        [self.delegate scoketReciveHistoryMessage:[NSString stringWithFormat:@"收到历史消息 %@",dic[@"seq"]]];
                    }
                } else {
                    if ([self.delegate respondsToSelector:@selector(socketAnalysisMessageFail:)]) {
                        [self.delegate socketAnalysisMessageFail:[NSString stringWithFormat:@"历史消息解压GZIP失败 %@",dic[@"seq"]]];
                    }
                }
                
            }
        }
    }
}


/**
 解析普通消息

 @param responsDic 消息字典
 */
- (void) analysisNomalMessage:(NSDictionary *) responsDic {
    NSDictionary *resultDic = responsDic[@"result"];
    NSNumber *type = resultDic[@"type"];
    if (!type) {
        NSDictionary *dataDic = resultDic[@"data"];
        NSString *channel = resultDic[@"channel"];
        ChannelModel *channel_temp = self.channelDic[channel];
        //更新频道的seq保证seq是最后一条消息的,用于历史消息的seq参数
        channel_temp.seq = dataDic[@"seq"];
        NSDictionary *inputData = dataDic[@"data"];
        NSString *inputStr = inputData[@"input"];
        if (inputStr) { //socket消息
            BOOL isUnzip = NO;
            isUnzip = [self unGZIPWithStr:inputStr];
            if (isUnzip) {
                if ([self.delegate respondsToSelector:@selector(socketReciveMessage:)]) {
                    [self.delegate socketReciveMessage:[NSString stringWithFormat:@"收到消息 seq: %@",(channel_temp.seq ? channel_temp.seq : @"不支持历史消息")]];
                }
            } else {
                if ([self.delegate respondsToSelector:@selector(socketAnalysisMessageFail:)]) {
                    [self.delegate socketAnalysisMessageFail:[NSString stringWithFormat:@"解压GZIP失败 seq: %@",(channel_temp.seq ? channel_temp.seq : @"不支持历史消息")]];
                }
            }
            
        }
    }
}



/**
 字典转json串
 @param dict 需要转成json的字典
 @return json字符串
 */
- (NSString *)convertToJsonData:(NSDictionary *)dict {  //字典转json串
    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];
    NSString *jsonString;
    if (!jsonData) {
        NSLog(@"%@",error);
    }else{
        jsonString = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];
    }
    NSMutableString *mutStr = [NSMutableString stringWithString:jsonString];
    NSRange range = {0,jsonString.length};  //去掉字符串中的空格
    [mutStr replaceOccurrencesOfString:@" " withString:@"" options:NSLiteralSearch range:range];
    NSRange range2 = {0,mutStr.length};     //去掉字符串中的换行符
    [mutStr replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:range2];
    return mutStr;
    
}

- (NSDictionary*) dictionaryWithJsonString:(NSString*)jsonString{ //json串转字典
    if(jsonString ==nil) {
        return nil;
    }
    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    NSError*err;
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
    if(err){
        NSLog(@"json解析失败：%@",err);
        if ([self.delegate respondsToSelector:@selector(socketAnalysisMessageFail:)]) {
            [self.delegate socketAnalysisMessageFail:[NSString stringWithFormat:@"socketJSON解析失败"]];
        }
        return nil;
    }
    return dic;
}

- (BOOL) unGZIPWithStr:(NSString *) inputStr {
    BOOL isSuccess = NO;
    
    NSString *base64String = inputStr;
    NSData *data = [[NSData alloc]initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSData *tempData = [LFCGzipUtility ungzipData:data];
    if (tempData) {
        isSuccess = YES;
    }
    return isSuccess;
}


@end
